package com.gtmonitor;

import java.text.DecimalFormat;
import java.text.NumberFormat;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;

import com.gtmonitor.graphics.GraphicsActivity;

public class GTmonitorService extends Service { 

    private final static String TAG = "GTmonitor";
    public final static int BATTERYTHRESHOLD = 10;
    
    private static GTmonitorService singleton = null;
    public static final int MAXSIZE = 500;
    public static final int NOTIFYID_CPU = 719860902;
    public static final int NOTIFYID_MEM = 819860902;
    public static final int NOTIFYID_DSK = 919860902;
    public static final int NOTIFYID_NET = 619860902;
    public static final int NOTIFYID_DUM = 519860902;
    public static Notification cpuNotifier = null;
    public static Notification memNotifier = null;
    public static Notification dskNotifier = null;
    public static Notification netNotifier = null;
    public static NotificationManager manager = null;
    
    public final IBinder mBinder = new GTmonitorBinder();
    private Handler mHandler = new Handler();
    private static int interval = 1;
    public static Context serviceContext = null;
    public static int numOfStorage = 0;
    public static byte CPULoad;
    public static byte CPUSelfLoad;
    public static byte memUsage;
    public static int netHighestReceiveSpeedIndex = 0;
    public static float netHighestReceiveSpeed = 0;
    public static int netHighestTransmitSpeedIndex = 0;
    public static float netHighestTransmitSpeed = 0;
    private static NumberFormat formatter = new DecimalFormat("#0.00");
    public static long netTotalReceived;
    public static long netTotalTransmitted;
    public static float netTotalReceiveSpeed;
    public static float netTotalTransmitSpeed;
    public static StringBuilder CPULoadText = null;
    public static StringBuilder memUsageText = null;
    public static StringBuilder storageInfoText = null;
    public static StringBuilder networkInfoText = null;
    public static StringBuilder networkShortInfo = null;
    public static StringBuilder storageOnBarText = null;
    public static String[] storageNames = null;
    public static int storageOnBar = 0;
    public static int networkOnBar = 0; // 0: out; 1: in; 2: out
    public static boolean CPULogOn = false;
    public static boolean excludeSelfCPULoad = false;
    public static boolean storageLogOn = false;
    public static boolean memoryLogOn = false;
    public static boolean networkLogOn = false;
    public static boolean turnOnAllLogging = false;
    public static boolean showCPUBarIcon = false;
    public static boolean showMemBarIcon = false;
    public static boolean showDskBarIcon = false;
    public static boolean showNetBarIcon = false;
    public static boolean powerSaving = false;
    public static boolean loggable = true;
    public static SharedPreferences settings = null;
    public static int icon;
    public static CharSequence tickerText;
    public static CharSequence contentTitle;
    public static PendingIntent contentIntent;
    private static JNIInterface JNILibrary = JNIInterface.getInstance();
    
    // Stored Data
    // Global 
    public static int index = 0;
    // CPU
    public static byte[] CPULoadArray = null;
    public static byte[] CPUSelfLoadArray = null;
    // Storage
    public static byte[][] storageUsageArray = null;
    // Memory
    public static byte[] memUsageArray = null;
    // Network
    public static float[] netReceiveUtilArray = null;
    public static float[] netTransmitUtilArray = null;

    // The onDestroy call is not guaranteed, so call cancelAll manually
    public static void cancelAllNotifications() {
        manager.cancelAll();
    }
     
    public static GTmonitorService getInstance() {
        
        return singleton;
    }
    
    private static void updateCPULoad() {
        CPULoadArray[index] = CPULoad;
        CPUSelfLoadArray[index] = CPUSelfLoad;
    }
    
    private static void updateMemUsage() { 
        memUsageArray[index] = memUsage;
    }
    
    private static void updateStorageUsage(int id, byte usage) {
        storageUsageArray[id][index] = usage;
    }
    
    private static void updateNetworkUsage(float rx, float tx) {
        netReceiveUtilArray[index] = rx;
        netTransmitUtilArray[index] = tx;
    }
    
    private Runnable mRefresh = new Runnable() {
        @Override
        public void run() {
            if (powerSaving) {
                int powcap = JNILibrary.GetPowerCapacity();
                if (powcap < BATTERYTHRESHOLD && powcap > 0) {
                    GraphicsActivity.logCheckBox.setEnabled(false);
                    JNILibrary.hold();
                    GTmonitorService.cancelAllNotifications();
                    stopSelf();
                    GraphicsActivity.loggable = false;
                    return;
                }
            }
            
            // Update Network
            char rxUnit = 'K';
            char txUnit = 'K';
            netTotalReceived = JNILibrary.getNetTotalRx();
            if (netTotalReceived > 1024) {
                netTotalReceived /= 1024;
                rxUnit = 'M';
            }
            netTotalTransmitted = JNILibrary.getNetTotalTx();
            if (netTotalTransmitted > 1024) {
                netTotalTransmitted /= 1024;
                txUnit = 'M';
            } 
            netTotalReceiveSpeed = JNILibrary.getNetTotalRxSpeed() / 1024;
            netTotalTransmitSpeed  = JNILibrary.getNetTotalTxSpeed() / 1024;
            updateNetworkUsage(netTotalReceiveSpeed, netTotalTransmitSpeed);
             
            networkInfoText.setLength(9); 
            networkInfoText.append("Rx:")
                           .append(netTotalReceived).append(rxUnit)
                           .append(" Tx:")
                           .append(netTotalTransmitted).append(txUnit)
                           .append("<br /><font color=\"red\">Rx/s:")
                           .append(formatter.format(netTotalReceiveSpeed))
                           .append("K/s</font> <font color=\"green\">Tx/s:")
                           .append(formatter.format(netTotalTransmitSpeed))
                           .append("K/s</font>");
            networkShortInfo.setLength(9);
            networkShortInfo.append("Rx/s:")
                            .append(formatter.format(netTotalReceiveSpeed))
                            .append("K/s Tx:")
                            .append(formatter.format(netTotalTransmitSpeed))
                            .append("K/s");
            if (showNetBarIcon) {
                if (netTotalReceiveSpeed == 0.0 && netTotalTransmitSpeed == 0.0)
                    netNotifier.iconLevel = 2000;
                else if (netTotalReceiveSpeed == 0.0 && netTotalTransmitSpeed > 0.0) 
                    netNotifier.iconLevel = 2002;
                else if (netTotalReceiveSpeed > 0.0 && netTotalTransmitSpeed == 0.0)
                    netNotifier.iconLevel = 2001;
                else netNotifier.iconLevel = 2003;
                netNotifier.setLatestEventInfo(serviceContext, networkShortInfo, 
                            null, netNotifier.contentIntent);
                manager.notify(NOTIFYID_NET, netNotifier);
                
            }
            
            // Update CPU
            CPULoad = (byte)JNILibrary.getCPUUsage();
            CPUSelfLoad = (byte)JNILibrary.getSelfLoad();
            int excludeSelf = CPULoad - CPUSelfLoad;
            int value = 0;
            updateCPULoad();
            CPULoadText.setLength(5);
            if (excludeSelfCPULoad) {
                value = excludeSelf;
                CPULoadText.append((int)excludeSelf).append("%");
            } else {
                value = CPULoad;
                CPULoadText.append((int)CPULoad).append("%");
            }
            if (showCPUBarIcon) {
                cpuNotifier.iconLevel = value / 10;
                cpuNotifier.setLatestEventInfo(serviceContext, CPULoadText,
                        null, cpuNotifier.contentIntent);
                manager.notify(NOTIFYID_CPU, cpuNotifier);
            }
            
            // Update Memory
            char freeUnit = 'K';
            char totalUnit = 'M';
            long memTotal = 0, memInUse = 0;
            memTotal = JNILibrary.getMemTotal();
            memUsageText.setLength(14);
            if (memTotal != 0) {
                long memCache = JNILibrary.getMemCached();
                long memFree = JNILibrary.getMemFree();
                long memBuffer = JNILibrary.getMemBuffers();
                long memFreeTotal = (memCache + memFree + memBuffer);
                memInUse = memTotal - memFreeTotal;
                if (memInUse > 1024) {
                    memInUse /= 1024;
                    freeUnit = 'M';
                }
                if (memTotal > 1024) {
                    memTotal /= 1024;
                    totalUnit = 'M'; 
                }  
                memUsage = (byte)(memInUse * 100 / memTotal);
                updateMemUsage();
                memUsageText.append(memInUse).append(freeUnit).append("/").append(memTotal).append(totalUnit);
            } else {
                memUsage = 0;
                memUsageText.append("N/A");
            }
            if (showMemBarIcon) {
                memNotifier.iconLevel = memUsage / 10 + 100;
                memNotifier.setLatestEventInfo(serviceContext, memUsageText, 
                            null, memNotifier.contentIntent);
                manager.notify(NOTIFYID_MEM, memNotifier);
                
            }
            
            // Update Storage
            int itmp = JNILibrary.getStorageTotal();
            if (numOfStorage != itmp) {
                storageNames = new String[itmp];
                for (int i=0; i<itmp; ++i) {
                    storageNames[i] = new String(JNILibrary.getStorageName(i));
                    Log.i(TAG, "add new storage: " + storageNames[i]);
                }
            }
            if (numOfStorage < itmp) {
                Log.i(TAG, "GTmonitorService: current number of storages " + itmp);
                byte[][] tmp = new byte[itmp][MAXSIZE];
                if (storageUsageArray != null) {
                    for (int i=0; i<itmp; ++i) {
                        for (int j=0; j<MAXSIZE; ++j)
                            tmp[i][j] = storageUsageArray[i][j];
                    }
                }
                storageUsageArray = tmp;
            } 
            numOfStorage = itmp;
            storageInfoText.setLength(9);
            storageInfoText.append(numOfStorage).append("\n");
            int storageOnBarValue = 0;
            for (int i=0; i<numOfStorage; ++i) {
                byte storageUsage = (byte)JNILibrary.getStorageUtil(i);
                updateStorageUsage(i, storageUsage);
                storageInfoText.append(storageNames[i])
                               .append(": ")
                               .append((int)storageUsage)
                               .append("% ");
                if (i == storageOnBar) {
                    storageOnBarValue = storageUsage;
                    storageOnBarText.setLength(0);
                    storageOnBarText.append(storageNames[i])
                                    .append(": ")
                                    .append((int)storageUsage).append("%");
                }
            }
            if (showDskBarIcon) {
                dskNotifier.iconLevel = storageOnBarValue / 10 + 1000;
                dskNotifier.setLatestEventInfo(serviceContext, storageOnBarText, 
                            null, dskNotifier.contentIntent);
                manager.notify(NOTIFYID_DSK, dskNotifier);
            }
            
            // Update Index
            int tmp = (index + 1) % MAXSIZE;
            index = tmp;
            
            mHandler.postDelayed(mRefresh, interval * 1000);
        }
    };

    @Override
    public void onCreate() {
        singleton = this;
        JNILibrary = JNIInterface.getInstance();
        //if (GTmonitorActivity.screenWidth > MAXSIZE) 
        //    MAXSIZE = GTmonitorActivity.screenWidth;
        //Log.d(TAG, "MAXSIZE " + MAXSIZE);
        
        CPULoadArray = new byte[MAXSIZE];
        CPUSelfLoadArray = new byte[MAXSIZE];
        memUsageArray = new byte[MAXSIZE];
        netReceiveUtilArray = new float[MAXSIZE];
        netTransmitUtilArray = new float[MAXSIZE];
        
        manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
        
        CPULoadText = new StringBuilder("CPU: ");
        memUsageText = new StringBuilder("Memory Usage: ");
        storageInfoText = new StringBuilder("Storage: ");
        networkInfoText = new StringBuilder("Network: ");
        networkShortInfo = new StringBuilder("Network: ");
        storageOnBarText = new StringBuilder();
        index = 0;
        init();
        mHandler.post(mRefresh);
        Log.i("GTmonitor", "GTmonitorService: oncreate");
    }
    
    public void resume(SharedPreferences settings) {
        JNILibrary.resume();
        restorePreferencesStatusBar(settings);
        mHandler.post(mRefresh);
    }

    public void restorePreferencesStatusBar(SharedPreferences settings) {
        long sysTime = System.currentTimeMillis();
        boolean tmp = settings.getBoolean(Preferences.PREF_SHOWCPUBARICON, false);
        if (tmp) {
            if (cpuNotifier == null) {
                cpuNotifier = new Notification(icon, tickerText, sysTime);
                cpuNotifier.flags |= Notification.FLAG_NO_CLEAR|Notification.FLAG_ONGOING_EVENT;
                cpuNotifier.contentIntent = contentIntent;
                cpuNotifier.setLatestEventInfo(this, contentTitle, null, contentIntent);
                manager.notify(NOTIFYID_CPU, cpuNotifier);
                startForeground(NOTIFYID_CPU, cpuNotifier);
                
            }
        } else { 
            manager.cancel(NOTIFYID_CPU);
        }
        showCPUBarIcon = tmp;
          
        tmp = settings.getBoolean(Preferences.PREF_SHOWMEMBARICON, false);
        if (tmp) {
            if (memNotifier == null) {
                memNotifier = new Notification(icon, tickerText, sysTime);
                memNotifier.flags |= Notification.FLAG_NO_CLEAR|Notification.FLAG_ONGOING_EVENT;
                memNotifier.contentIntent = contentIntent;
                memNotifier.setLatestEventInfo(this, contentTitle, null, contentIntent);
                manager.notify(NOTIFYID_MEM, memNotifier);
                startForeground(NOTIFYID_MEM, memNotifier);
            }
        } else {
            manager.cancel(NOTIFYID_MEM);
        }
        showMemBarIcon = tmp;
        
        tmp = settings.getBoolean(Preferences.PREF_SHOWDSKBARICON, false);
        if (tmp) {
            if (dskNotifier == null) {
                dskNotifier = new Notification(icon, tickerText, sysTime);
                dskNotifier.flags |= Notification.FLAG_NO_CLEAR|Notification.FLAG_ONGOING_EVENT;
                dskNotifier.contentIntent = contentIntent;
                dskNotifier.setLatestEventInfo(this, contentTitle, null, contentIntent);
                manager.notify(NOTIFYID_DSK, dskNotifier);
                startForeground(NOTIFYID_DSK, dskNotifier);
            }
        } else {
            manager.cancel(NOTIFYID_DSK);
        }
        showDskBarIcon = tmp;   
        
        tmp = settings.getBoolean(Preferences.PREF_SHOWNETBARICON, false);
        if (tmp) {
            if (netNotifier == null) {
                netNotifier = new Notification(icon, tickerText, sysTime);
                netNotifier.flags |= Notification.FLAG_NO_CLEAR|Notification.FLAG_ONGOING_EVENT;
                netNotifier.contentIntent = contentIntent;
                netNotifier.setLatestEventInfo(this, contentTitle, null, contentIntent);
                manager.notify(NOTIFYID_NET, netNotifier);
                startForeground(NOTIFYID_NET, netNotifier);
            }
        } else {
            manager.cancel(NOTIFYID_NET);
        }
        showNetBarIcon = tmp;
        
        powerSaving = settings.getBoolean(Preferences.PREF_POWERSAVINGMODE, false);
    }
    
    private void init() {
        icon = R.anim.storage;
        serviceContext = this;
        tickerText = getResources().getString(R.string.barTitle);
        contentTitle = getResources().getString(R.string.appName);
        
        Intent notificationIntent = new Intent(this, GTmonitorActivity.class);
        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
        contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        
        settings = PreferenceManager.getDefaultSharedPreferences(this);
        restorePreferencesStatusBar(settings);
    } 
    
    @Override
    public void onDestroy() {
        manager.cancelAll();
    }
    
    public class GTmonitorBinder extends Binder {
        public GTmonitorService getService() {
            return GTmonitorService.this;
        }
    }
    
    @Override
    public IBinder onBind(Intent intent) {
            return mBinder;
    }
}
